package ar.com.e2solver.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;

import ar.com.e2solver.conf.PuzzleTheme;
import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.solver.RandomConstants;

public class Puzzle {
	
	private Piece[][] tableBoard;
	private int rows;
	private int cols;
	private Collection<Coordinate> fixedCoordinates;

	public Puzzle(PuzzleTheme puzzleTheme) {
		this.rows = puzzleTheme.getRows();
		this.cols = puzzleTheme.getCols();
		this.fixedCoordinates = new HashSet<Coordinate>();
		this.tableBoard = new Piece[rows][cols];
		
		for(int i = 0; i < rows; i++){
			for(int j = 0; j < cols; j++){
				this.tableBoard[i][j] = puzzleTheme.getPiece(i,j);
			}
		}
	}
	
	public Puzzle(Puzzle anotherPuzzle){
		this.tableBoard = new Piece[anotherPuzzle.getRowsNumber()][anotherPuzzle.getColumnNumbers()];
		this.cols = anotherPuzzle.getColumnNumbers();
		this.rows = anotherPuzzle.getRowsNumber();
		this.fixedCoordinates = new HashSet<Coordinate>(anotherPuzzle.getFixedCoordinates());
		
		for (int i = 0; i < anotherPuzzle.getRowsNumber(); i++) {
			for (int j = 0; j < anotherPuzzle.getColumnNumbers(); j++) {
				Piece anotherPuzzlePiece = anotherPuzzle.getPieceAt(i, j);
				Piece newPuzzlePiece = new Piece(anotherPuzzlePiece);
				tableBoard[i][j] = newPuzzlePiece;
			}
		}	
	}

	public Piece getPieceAt(int x, int y) {
		return tableBoard[x][y];
	}
	
	public Piece getPieceAt(Coordinate c){
		return getPieceAt(c.getX(), c.getY());
	}
	
	public void swapPositions(int x1, int y1, int x2, int y2) throws BusinessException{
		Coordinate c1 = new Coordinate(x1, y1);
		Coordinate c2 = new Coordinate(x2, y2);
		swapPositions(c1, c2);
	}
	

	public void swapPositions(Coordinate c1, Coordinate c2) throws BusinessException{
		
		if(getFixedCoordinates().contains(c1) || getFixedCoordinates().contains(c2)){
			throw new BusinessException("No se puede intercambiar una pieza fija");
		}
		
		Piece tmp = tableBoard[c1.getX()][c1.getY()];
		tableBoard[c1.getX()][c1.getY()] = tableBoard[c2.getX()][c2.getY()];
		tableBoard[c2.getX()][c2.getY()] = tmp;
	}
	
	public boolean canSwap(int x1, int y1, int x2, int y2){
		Coordinate c1 = new Coordinate(x1, y1);
		Coordinate c2 = new Coordinate(x2, y2);
		return canSwap(c1, c2);
	}
	
	public boolean canSwap(Coordinate c1, Coordinate c2){
		return !getFixedCoordinates().contains(c1) && !getFixedCoordinates().contains(c2);
	}

	public List<Piece> getPieces() {
		List<Piece> pieces = new ArrayList<Piece>();
		for (int i = 0; i < tableBoard.length; i++) {
			for (int j = 0; j < tableBoard[i].length; j++) {
				pieces.add(tableBoard[i][j]);
			}
		}
		return pieces;
	}
	
	public Coordinate findCoordinateForPiece(Piece aPiece) {
		Coordinate coordinate = null;
		
		for(int i = 0; i < getRowsNumber(); i++){
			for(int j = 0; j < getColumnNumbers(); j++){
				Piece puzzlePiece = getPieceAt(i, j);
				if(puzzlePiece.equals(aPiece)){
					coordinate = new Coordinate(i, j);
					break;
				}
			}
		}
		
		return coordinate;
	}
	
	public void shuffle(){
		Random random = RandomConstants.RANDOM;
		List<Piece> borderPieces = getBorderPieces();
		List<Piece> cornerPieces = getCornerPieces();
		List<Piece> internalPieces = getInternalPieces();

		Piece piece = null;
		int index = 0;
		for (int i = 0; i < getRowsNumber(); i++) {
			for (int j = 0; j < getColumnNumbers(); j++) {
				
				// es una esquina
				if(	(i == 0 && j == 0) || 
					(i == 0 && j == getColumnNumbers() -1) || 
					(i == getRowsNumber() -1 && j == 0) || 
					(i == getRowsNumber() -1 && j == getColumnNumbers() -1)){
				
					if(i == 0 && j == 0 && this.isSquare()){
						// evita que se generen las mismas soluciones rotadas
						index = 0;
						addFixedCoordinate(new Coordinate(0,0));
					} else {
						index = random.nextInt(cornerPieces.size());
					}
					piece = cornerPieces.get(index);
					cornerPieces.remove(index);
					rotateForCorner(i, j, piece);

				// es un borde	
				} else if(	(i == 0 && j != 0 && j != getColumnNumbers() -1) ||
							(i == getRowsNumber() -1 && j != 0 && j != getColumnNumbers() -1) ||
							(i != 0 && i != getRowsNumber() -1 && j == 0) ||
							(i != 0 && i != getRowsNumber() -1 && j == getColumnNumbers() -1)){
					index = random.nextInt(borderPieces.size());
					piece = borderPieces.get(index);
					borderPieces.remove(index);
					rotateForBorder(i, j, piece);

				// es interno		
				} else {
					index = random.nextInt(internalPieces.size());
					piece = internalPieces.get(index);
					internalPieces.remove(index);
					piece.rotateToRight(random.nextInt(3) + 1);
				}
				
				tableBoard[i][j] = piece;
			}
		}
	}
	
	public float compare(Puzzle p){
		int equals = 0;
		for(int i = 0; i < this.getRowsNumber(); i++){
			for (int j = 0; j < this.getColumnNumbers(); j++) {
				if(p.getPieceAt(i, j).equalPositionAndColor(this.getPieceAt(i, j))){
					equals++;
				}
			}
		}
		
		return (float)equals / (float)(this.getRowsNumber() * this.getColumnNumbers());
	}
	
	private List<Piece> getBorderPieces() {
		List<Piece> pieces = new ArrayList<Piece>();
		for (int i = 0; i < tableBoard.length; i++) {
			for (int j = 0; j < tableBoard[i].length; j++) {
				Piece piece = tableBoard[i][j];
				if(piece.isABorderPiece()){
					pieces.add(piece);
				}
			}
		}
		return pieces;
	}
	
	private List<Piece> getCornerPieces() {
		List<Piece> pieces = new ArrayList<Piece>();
		for (int i = 0; i < tableBoard.length; i++) {
			for (int j = 0; j < tableBoard[i].length; j++) {
				Piece piece = tableBoard[i][j];
				if(piece.isACornerPiece()){
					pieces.add(piece);
				}
			}
		}
		return pieces;
	}
	
	private List<Piece> getInternalPieces() {
		List<Piece> pieces = new ArrayList<Piece>();
		for (int i = 0; i < tableBoard.length; i++) {
			for (int j = 0; j < tableBoard[i].length; j++) {
				Piece piece = tableBoard[i][j];
				if(!piece.isACornerPiece() && !piece.isABorderPiece()){
					pieces.add(piece);
				}
			}
		}
		return pieces;
	}
	
	private void rotateForBorder(int i, int j, Piece piece) {
		if(i == 0 ){
			while(!piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
			
		} else if(i > j){
			while(!piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
			
		} else {
			while(!piece.getRight().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}	
		}
	}

	private void rotateForCorner(int i, int j, Piece piece) {
		if(i == 0 && j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR) || !piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(i == 0 && j != 0){
			while(!piece.getRight().equals(Piece.BORDER_COLOR) || !piece.getTop().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}
		} else if(i != 0 && j == 0){
			while(!piece.getLeft().equals(Piece.BORDER_COLOR) || !piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}			
		} else {
			while(!piece.getRight().equals(Piece.BORDER_COLOR) || !piece.getBottom().equals(Piece.BORDER_COLOR)){
				piece.rotateToRight(1);
			}			
		}
	}

	public int getRowsNumber() {
		return this.rows;
	}

	public int getColumnNumbers() {
		return this.cols;
	}
	
	public boolean isSquare() {
		return this.getColumnNumbers() == this.getRowsNumber();
	}

	public void addFixedCoordinate(Coordinate coordinate){
		this.fixedCoordinates.add(coordinate);
	}
	
	public void removeFixedCoordinate(Coordinate coordinate){
		this.fixedCoordinates.remove(coordinate);
	}
	
	public Collection<Coordinate> getFixedCoordinates() {
		return this.fixedCoordinates;
	}
	
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(this.getRowsNumber());
		sb.append("\n");
		sb.append(this.getColumnNumbers());
		sb.append("\n");
		for (int i = 0; i < this.getRowsNumber(); i++) {
			for (int j = 0; j < this.getColumnNumbers(); j++) {
				sb.append(this.getPieceAt(i,j));
				sb.append("\n");
			}
		}
		return sb.toString();
	}
	
}